using System;
using Microsoft.SPOT;
using Gadgeteer;

namespace G2Tech.Gadgeteer.MountaineerLed
{
	/// <summary>
	/// Converts to and from HSL
	/// - Hue, Saturation and Luminescence are all in the ranges 0 to 1
	/// <remarks>Algorithm from http://www.geekymonkey.com/Programming/CSharp/RGB2HSL_HSL2RGB.htm </remarks>
	/// </summary>
	public static class HSLHelper
	{
        #region Internal Methods

        /// <summary>
        /// Returns the smallest value
        /// </summary>
        /// <param name="value1">The first value</param>
        /// <param name="value2">The second value</param>
        /// <param name="value3">The third value</param>
        /// <returns>The smallest value</returns>
        internal static double Min(double value1, double value2, double value3)
        {
            value1 = value1 < value2 ? value1 : value2;
            return value1 < value3 ? value1 : value3;
        }

        /// <summary>
        /// Returns the largest value
        /// </summary>
        /// <param name="value1">The first value</param>
        /// <param name="value2">The second value</param>
        /// <param name="value3">The third value</param>
        /// <returns>The largest value</returns>
        internal static double Max(double value1, double value2, double value3)
        {
            value1 = value1 > value2 ? value1 : value2;
            return value1 > value3 ? value1 : value3;
        }

        /// <summary>
        /// Returns the absolute value
        /// </summary>
        /// <param name="value">The value</param>
        /// <returns>The absolute value</returns>
        internal static double Abs(double value)
        {
            return value < 0 ? -value : value;
        }

        /// <summary>
        /// Limits a value to a range
        /// </summary>
        /// <param name="value">The value to limit</param>
        /// <param name="minimum">The smallest value</param>
        /// <param name="maximum">The largest value</param>
        /// <returns>The limited value</returns>
        internal static double Clamp(double value, double minimum = 0, double maximum = 1)
        {
            return value < minimum ? minimum : value > maximum ? maximum : value;
        }

        /// <summary>
        /// Wraps a value if it goes outside of a range
        /// </summary>
        /// <param name="value">The value to wrap</param>
        /// <param name="minimum">The smallest value</param>
        /// <param name="maximum">The largest value</param>
        /// <returns>The wrapped value</returns>
        internal static double Wrap(double value, double minimum = 0, double maximum = 1)
        {
            double range = maximum - minimum;

            return value >= 0 ? minimum + value % range : minimum + range - Abs(value) % range;
        } 
        #endregion

		/// <summary>
		/// Converts hue, saturation and luminescence values to an RGB colour
		/// </summary>
		/// <param name="hsl">The HSL</param>
		/// <returns>The RGB colour</returns>
        public static Color HSL2RGB(HSLColor hsl)
		{
			double red = hsl.L;
            double green = hsl.L;
            double blue = hsl.L;
            double v = (hsl.L <= 0.5) ? (hsl.L * (1.0 + hsl.S)) : (hsl.L + hsl.S - hsl.L * hsl.S);
			if (v > 0)
			{
                double m = hsl.L + hsl.L - v;
				double sv = (v - m) / v;
                hsl.H *= 6.0;
                int sextant = (int)hsl.H;
                double fract = hsl.H - sextant;
				double vsf = v * sv * fract;
				double mid1 = m + vsf;
				double mid2 = v - vsf;
				switch (sextant)
				{
					case 0:
						red = v;
						green = mid1;
						blue = m;
						break;

					case 1:
						red = mid2;
						green = v;
						blue = m;
						break;

					case 2:
						red = m;
						green = v;
						blue = mid1;
						break;

					case 3:
						red = m;
						green = mid2;
						blue = v;
						break;

					case 4:
						red = mid1;
						green = m;
						blue = v;
						break;

					case 5:
						red = v;
						green = m;
						blue = mid2;
						break;
				}
			}

			return new Color((byte)(red * 255.0), (byte)(green * 255.0), (byte)(blue * 255.0));
		}

		/// <summary>
		/// Converts an RGB colour to hue, saturation and luminescence values
		/// </summary>
		/// <param name="rgb">The RGB colour</param>
        /// <returns>The HSL colour</returns>
        public static HSLColor RGB2HSL(Color rgb)
		{
			double red = rgb.R / 255.0;
			double green = rgb.G / 255.0;
			double blue = rgb.B / 255.0;

            HSLColor hsl;

            hsl.H = 0;
            hsl.S = 0;

			double v = Max(red, green, blue);
			double m = Min(red, green, blue);
            hsl.L = (m + v) / 2.0;
            if (hsl.L <= 0.0)
			{
                return hsl;
			}

			double vm = v - m;
            hsl.S = vm;
            if (hsl.S > 0.0)
			{
                hsl.S /= (hsl.L <= 0.5) ? (v + m) : (2.0 - v - m);
			}
			else
			{
                return hsl;
			}

			double red2 = (v - red) / vm;
			double green2 = (v - green) / vm;
			double blue2 = (v - blue) / vm;
			if (red == v)
			{
                hsl.H = (green == m ? 5.0 + blue2 : 1.0 - green2);
			}
			else if (green == v)
			{
                hsl.H = (blue == m ? 1.0 + red2 : 3.0 - blue2);
			}
			else
			{
                hsl.H = (red == m ? 3.0 + green2 : 5.0 - red2);
			}

            hsl.H /= 6.0;

            return hsl;
		}
        
		/// <summary>
		/// Increases the hue of a colour by the amount
		/// </summary>
		/// <param name="colour">The colour to change</param>
		/// <param name="amount">The amount to increase the hue by, defaults to 1/6 (60 degrees)</param>
        /// <returns>The adjusted color</returns>
        public static Color IncreaseHue(Color color, double amount = 0.167)
		{
            var hsl = RGB2HSL(color);

            hsl.H += amount;
            hsl.H = Wrap(hsl.H);

            return HSL2RGB(hsl);
		}

        /// <summary>
        /// Decrease the hue of a colour by the amount
        /// </summary>
        /// <param name="colour">The colour to change</param>
        /// <param name="amount">The amount to decrease the hue by, defaults to -1/6 (60 degrees)</param>
        /// <returns>The adjusted color</returns>
        public static Color DecreaseHue(Color color, double amount = -0.167)
        {
            return IncreaseHue(color, amount);
        }


		/// <summary>
		/// Increases the saturation of a colour by the amount
		/// </summary>
		/// <param name="colour">The colour to change</param>
		/// <param name="amount">The amount to increase the saturation by, defaults to 0.1</param>
        /// <returns>The adjusted color</returns>
        public static Color IncreaseSaturation(Color color, double amount = 0.1)
		{
            var hsl = RGB2HSL(color);

            hsl.S += amount;
            hsl.S = Clamp(hsl.S);

            return HSL2RGB(hsl);
		}

        /// <summary>
        /// Decrease the saturation of a colour by the amount
        /// </summary>
        /// <param name="colour">The colour to change</param>
        /// <param name="amount">The amount to decrease the saturation by, defaults to 0.1</param>
        /// <returns>The adjusted color</returns>
        public static Color DecreaseSaturation(Color color, double amount = -0.1)
        {
            return IncreaseSaturation(color, amount);
        }


		/// <summary>
		/// Increases the luminescence of a colour by the amount
		/// </summary>
		/// <param name="colour">The colour to change</param>
		/// <param name="amount">The amount to increase the luminescence by, defaults to 0.1</param>
		/// <returns>The adjusted colour</returns>
		public static Color IncreaseLuminescence(Color color, double amount = 0.1)
		{
            var hsl = RGB2HSL(color);

            hsl.L += amount;
            hsl.L = Clamp(hsl.L);

            return HSL2RGB(hsl);
		}
	
        /// <summary>
		/// Decrease the luminescence of a colour by the amount
		/// </summary>
		/// <param name="colour">The colour to change</param>
		/// <param name="amount">The amount to decrease the luminescence by, defaults to 0.1</param>
		/// <returns>The adjusted color</returns>
        public static Color DecreaseLuminescence(Color color, double amount = -0.1)
        {
            return IncreaseLuminescence(color, amount);
        }


        /// <summary>
        /// HSL Extension Method
        /// </summary>
        /// <param name="hsl"></param>
        /// <returns></returns>
        public static Color ToColor(this HSLColor hsl)
        {
            return HSL2RGB(hsl);
        }


        /// <summary>
        /// Color Extension Method
        /// </summary>
        /// <param name="hsl"></param>
        /// <returns></returns>
        public static HSLColor ToHSL(this Color color)
        {
            return RGB2HSL(color);
        }
    }
}

